//========================================================//
// Cryptography realization   		                      //
// Author: Kozlov Dmitry aka LandFox kozlov@ccfit.nsu.ru  //
// NSU FIT September - October 2004                       //
//========================================================//

#include "icrypto.h"

#define SMALL_PRIV_KEY
#define SMALL_KEY_SIZE 21

namespace BAM {

	void cMD5Transform::setKey(const cPacket &P) {
		throw EXC_NoKey;
	}

	dword cMD5Transform::getKeyLength() {
		return 16;
	}

	cPacket cMD5Transform::transform(const cPacket &P) {
		MD5_CTX ctx;
		cPacket result;

		result.setLength(16);
		MD5_Init(&ctx);
		MD5_Update(&ctx,P.getData(),P.getLength());
		MD5_Final(result.getWData(),&ctx);

		return result;
	}

	cHMAC_MD5Transform::cHMAC_MD5Transform() {
		vHMacKey.setLength(64);
	}

	cHMAC_MD5Transform::~cHMAC_MD5Transform() 
	{
		
	}

	void cHMAC_MD5Transform::setKey(const cPacket &P, bool nmd5) {
		byte newKey[64];
		dword KeyLength;
		MD5_CTX md5ctx;

		for (int i = 0; i < 64 ;i ++) newKey[i] = 0;
		KeyLength = P.getLength();

		if (KeyLength > 64 || nmd5) { // add in condition || nmd5 //K = H(K) {RFC 2104}
			MD5_Init(&md5ctx);
			MD5_Update(&md5ctx,P.getData(),KeyLength);
			MD5_Final(newKey,&md5ctx);
			vHMacKey.write(newKey,64);
		} else { //B = 64 {RFC 2104}	
			P.read(newKey,KeyLength);
			vHMacKey.write(newKey,64);
		}
	}

	dword cHMAC_MD5Transform::getKeyLength() {
		return 16;
	}

	cPacket cHMAC_MD5Transform::transform(const cPacket &P) {

		const byte * key = vHMacKey.getData();
		byte ipad[64],opad[64];
		byte digest[16];
		MD5_CTX md5ctx;
		int i;

		for (i = 0; i < 16; i++) {
			((unsigned long *)ipad)[i] = 0x36363636 ^ ((unsigned long *)key)[i];
			((unsigned long *)opad)[i] = 0x5c5c5c5c ^ ((unsigned long *)key)[i];
		}

		MD5_Init(&md5ctx);
		MD5_Update(&md5ctx,ipad,64);
		MD5_Update(&md5ctx,P.getData(),P.getLength());
		MD5_Final(digest,&md5ctx);
		MD5_Init(&md5ctx);
		MD5_Update(&md5ctx,opad,64);
		MD5_Update(&md5ctx,digest,16);
		MD5_Final(digest,&md5ctx);
		return cPacket(digest,16);
	}

	void c3DES_ENCTransform::setKey(const cPacket &P) {
		MD5_CTX md5ctx;
		byte digest[16];

		MD5_Init(&md5ctx);
		MD5_Update(&md5ctx,P.getData(),P.getLength());
		MD5_Final(digest,&md5ctx);

		des3_set_2keys(&v3DES_CTX,digest,digest + 8);
		memset(vIVPacket, 0, 8);//set init equal 0
	}

	void c3DES_ENCTransform::setKey(const cPacket &Key, const cPacket &IV) {
		if (IV.getLength() != 8 || Key.getLength() != getKeyLength()) throw EXC_WrongKeySize;
		des3_set_2keys(&v3DES_CTX,Key.getData(),Key.getData() + 8);
		IV.read(vIVPacket,8);
	}

	dword c3DES_ENCTransform::getKeyLength() {
		return 16;
	}

	cPacket c3DES_ENCTransform::transform(const cPacket &P) {
		int count,pad,i,j;
		const byte * data;
		byte vLPacket[8];

		memcpy(vLPacket,vIVPacket,8);
		data = P.getData();
		count = P.getLength() >> 3;
		pad = P.getLength() & 0x07;

		cPacket result((count + 1) << 3);
		result.setLength((count + 1) << 3);

		for (j = 0; j < (count << 3); j += 8) {
			*((unsigned long *)vLPacket) ^= *((unsigned long *)data);
			((unsigned long *)vLPacket)[1] ^= ((unsigned long *)data)[1];
			des3_encrypt(&v3DES_CTX,vLPacket,vLPacket);//in vLPacket cipher text
			result.write(vLPacket,8, j);
			data += 8;
		}

		for ( i = 0 ; i < pad; i ++) vLPacket[i] ^= data[i];
		for ( ; i < 8 ; i ++ ) vLPacket[i] ^= 8 - pad;
		des3_encrypt(&v3DES_CTX,vLPacket,vLPacket);
		result.write(vLPacket,8, j);
		return result;
	}

	void c3DES_DECTransform::setKey(const cPacket &P) {
		MD5_CTX md5ctx;
		byte digest[16];

		MD5_Init(&md5ctx);
		MD5_Update(&md5ctx,P.getData(),P.getLength());
		MD5_Final(digest,&md5ctx);

		des3_set_2keys(&v3DES_CTX,digest,digest + 8);
		memset(vIVPacket, 0, 8);
	}

	void c3DES_DECTransform::setKey(const cPacket &Key, const cPacket &IV) {
		if (Key.getLength() != getKeyLength() || IV.getLength() != 8) throw EXC_WrongKeySize;
		des3_set_2keys(&v3DES_CTX,Key.getData(),Key.getData() + 8);
		IV.read(vIVPacket,8);

	}

	dword c3DES_DECTransform::getKeyLength() {
		return 16;
	}

	cPacket c3DES_DECTransform::transform(const cPacket &P) {
		int count,j;
		const byte * data;
		byte vLPacket[8],vCryptBuf[8];

		memcpy(vLPacket,vIVPacket,8);
		count = P.getLength() >> 3;
		if ((P.getLength() & 0x07) || (!P.getLength())) throw EXC_WrongDataSize;//update at 23.11.2004 by LandFox
		data = P.getData();

		cPacket result(count << 3);
		result.setLength(count << 3);

		for (j = 0; j < (count << 3) ; j += 8) {//last cicle decrypt some holl
			des3_decrypt(&v3DES_CTX,data,vCryptBuf);

			*((unsigned long *)vCryptBuf) ^= *((unsigned long *)vLPacket);
			((unsigned long *)vCryptBuf)[1] ^= ((unsigned long *)vLPacket)[1];

			result.write(vCryptBuf,8,j);
			memcpy(vLPacket,data,8);
			data += 8;
		}

		int nlen = result.getLength() - *(vCryptBuf + 7);
		if (nlen < 0)
			throw EXC_WrongDecrypt;
		result.setLength(nlen);
		return result;
	}

	cPacket cRandGenerator::GeneratePacket(dword length) {
		cPacket Result;
		dword i;
		byte * data;

		Result.setLength(length);
		data = Result.getWData();
		for(i = 0 ; i < length; i ++) {
			data[i] = rand();
		}

		return Result;
	}

	void cRandGenerator::GenerateBlock(byte * block, dword length) {//deprecated
		dword i;

		for(i = 0; i < length; i ++) {
			block[i] = rand();
		}
	}

	cRandGenerator& rndGen()
	{
		static cRandGenerator ra;
		return ra;
	}

	cPacket cLoginManager::getPassword(const cPacket &login)
	{
		if(login.getLength() == 5 && 
			memcmp(login.getData(), "admin", sizeof(byte)*5) == 0)
		{
			cPacket Pass;
			Pass.insertTail((byte*)"12345", 5);
			return Pass;
		}
		throw EXC_NoLogin;
	}

	void cLoginManager::initTransform(iCryptoTransform *Trans, const cPacket &login, bool hash)
	{
		if(hash)
			Trans->setKey(cMD5Transform().transform(getPassword(login)));
		else
			Trans->setKey(getPassword(login));
	}

	cLoginManager* cLoginManager::clone() const
	{
		return new cLoginManager();
	}

	cDHManager::cDHManager() {
		cCryptIntData base("2",1,true);
		vPub.setLength(128);
#ifndef SMALL_PRIV_KEY	
		vPriv = rndGen().GeneratePacket(128);
		vPriv.setLength(128);
#else
		cPacket vRand(SMALL_KEY_SIZE);
		vRand = rndGen().GeneratePacket(SMALL_KEY_SIZE);
		vPriv.setLength(128);
		byte * wdata = vPriv.getWData();
		memset(wdata,0,128);
		memcpy(wdata,vRand.getData(),SMALL_KEY_SIZE);
#endif
		vManager = new cCryptIntManager(cCryptIntData((const char *)SECURE_DH_1024,sizeof(SECURE_DH_1024) - 1,true));
		cCryptIntData cryptpriv ((const char *)vPriv.getData(),128);
		vManager->exp(base,cryptpriv);
		vPub.write(base.getData(),128,0);
		reverse(vPub.getWData());
	}

	cDHManager::~cDHManager() {
		delete vManager;
	}

	void cDHManager::reverse(unsigned char * data) const {
		int i;
		unsigned char change;

		for( i = 0; i < 64; i++) {
			change = data[i];
			data[i] = data[127 - i];
			data[127 - i] = change;
		}
	}

	cPacket cDHManager::getPublic() const {
		return vPub;
	}

	cPacket cDHManager::agree(const cPacket& pub) const {
		cPacket aPacket;
		if (pub.getLength() != DHKEYLen) throw EXC_WrongKeySize;
		aPacket.setLength(128);
		cCryptIntData pubkey ((const char *)pub.getData(),128);
		reverse(pubkey.getData());
		cCryptIntData prikey ((const char *)vPriv.getData(),128);
		vManager->exp(pubkey,prikey);
		aPacket.write(pubkey.getData(),128,0);
		reverse(aPacket.getWData());
		return aPacket;
	}

} //End namespace
